package cn.uncode.baas.server.internal;

import javax.script.ScriptException;

import org.apache.commons.lang3.StringUtils;

import cn.uncode.baas.server.acl.Acl;
import cn.uncode.baas.server.acl.token.AccessToken;
import cn.uncode.baas.server.cache.SystemCache;
import cn.uncode.baas.server.constant.Resource;
import cn.uncode.baas.server.database.DBContextHolder;
import cn.uncode.baas.server.dto.RestApp;
import cn.uncode.baas.server.dto.RestMethod;
import cn.uncode.baas.server.exception.AuthorizationException;
import cn.uncode.baas.server.exception.MethodNotFoundException;
import cn.uncode.baas.server.exception.ValidateException;
import cn.uncode.baas.server.internal.context.RestContextManager;
import cn.uncode.baas.server.internal.message.Messages;
import cn.uncode.baas.server.internal.module.IModules;
import cn.uncode.baas.server.internal.module.data.DataParamResolve;
import cn.uncode.baas.server.internal.script.ScriptExecuter;
import cn.uncode.baas.server.internal.script.ScriptResult;
import cn.uncode.baas.server.internal.validator.ValidatorExecuter;
import cn.uncode.baas.server.utils.AccessTokenUtils;
import cn.uncode.baas.server.utils.WatchUtils;
import cn.uncode.baas.server.utils.WebApplicationContextUtil;
import cn.uncode.dal.cache.CacheManager;

public class Executer {

	/**
	 * script execute
	 * 
	 * @param bucket
	 *            标识
	 * @param restName
	 *            名称
	 * @param option
	 *            操作类型
	 * @param version
	 *            版本
	 * @param maps
	 *            参数
	 * @param cacheEnable
	 *            是否使用缓存
	 * @return scriptResult 执行结果
	 * @throws NoSuchMethodException
	 * @throws ValidateException
	 * @throws ScriptException
	 * @throws MethodNotFoundException
	 */
	public static ScriptResult execute(String bucket, String restName, String option, String version,
			RequestMap<String, Object> maps, boolean cacheEnable, IModules modules, RequestMap<String, Object> head) throws NoSuchMethodException,
	        ValidateException, ScriptException, MethodNotFoundException {
		
		CacheManager cacheManager = WebApplicationContextUtil.getBean("cacheManager", CacheManager.class);
		ScriptResult scriptResult = null;
		// 1 check method
		RestMethod restMethod = SystemCache.getRestMethod(bucket, restName, option, version);
		if (restMethod == null) {
			throw new MethodNotFoundException(Messages.getString("RuntimeError.8", version, restName, bucket));
		}
		RestApp restApp = SystemCache.getRestApp(bucket);
		if (restApp == null || restApp.getStatus() == Resource.REST_APP_STATUS_DISABLE) {
			throw new MethodNotFoundException(Messages.getString("RuntimeError.11", restApp.getBucket()));
		}
		// 2.2 check method
		Acl acl = restMethod.getRestAcl();
		if(null != acl){
			if(!RestContextManager.getContext().containsAccessToken()){
				throw new AuthorizationException(Messages.getString("ValidateError.10"));
			}
			AccessToken accessToken = RestContextManager.getContext().getToken();
			if(!AccessTokenUtils.checkPermissions(acl, accessToken)){
				throw new AuthorizationException(Messages.getString("ValidateError.10"));
			}
		}
		WatchUtils.lap("switch-database-and-get-rest-method");
		// 3 authentication access token
		if (StringUtils.isNotBlank(restMethod.getAcl())) {
			AccessToken token = RestContextManager.getContext().getToken();
			if (null == token || token.isExpired()) {
				throw new ValidateException(Messages.getString("RuntimeError.10"));
			}
			if (!bucket.equals(RestContextManager.getContext().getBucket())) {
				throw new ValidateException(Messages.getString("RuntimeError.11"));
			}
			WatchUtils.lap("authentication-access-token");
		}
		
		// 4 validate parameters of method
		maps = ValidatorExecuter.validate(restMethod.getParamType(), restMethod.getRestFields(), maps);
		// 5 query cache
		String restKey = restMethod.getKey() + "_executer_" + maps.hashCode();
		if (cacheEnable && (restMethod.getSeconds() != -1)) {
			if (cacheManager != null && cacheManager.getCache() != null) {
				scriptResult = (ScriptResult) cacheManager.getCache().getObject(restKey);
				WatchUtils.lap("query-script-cache");
				if(scriptResult != null){
					return scriptResult;
				}
			}
		}
		// 6.1 switch database
		DBContextHolder.setDbType(bucket);
		// 6.2 execute script method
		WatchUtils.lap("execute-script-method-start");
		addCacheProperties(maps, restMethod.getSeconds());
		scriptResult = ScriptExecuter.execute(maps, modules, restMethod.getScript(), null, restMethod.getKey(), head);
		if (scriptResult != null && StringUtils.isNotBlank(scriptResult.getErr())) {
			throw new ValidateException(Messages.getString("RuntimeError.9", version, restName, bucket,
			        scriptResult.getErr()));
		}
		WatchUtils.lap("execute-script-method-end");
		// 7 store cache
		if (cacheEnable && (-1 != restMethod.getSeconds())) {
			if (cacheManager != null && cacheManager.getCache() != null) {
				if (restMethod.getSeconds() > 0) {
					cacheManager.getCache().putObject(restKey, scriptResult, restMethod.getSeconds());
				} else {
					cacheManager.getCache().putObject(restKey, scriptResult);
				}
				WatchUtils.lap("store-script-cache");
			}
		}
		
		// 8 clear context
		DBContextHolder.clearDbType();
		return scriptResult;
	}

	/**
	 * 添加generic service cache
	 * 该层缓存禁用，因为依赖于table名称
	 * @param maps
	 * @param seconds
	 * @return
	 */
	private static RequestMap<String, Object> addCacheProperties(RequestMap<String, Object> maps, int seconds) {
		maps.put(DataParamResolve.FIELD_TIME_OUT, seconds);
		return maps;
	}

}
